The sort algorithm sample code in the preceding
section allows the three sort algorithms to run to completion. But
logically, because all the sort algorithms wind up with the same
results, you need to sort the integer collection only once—the fastest
way possible. When the fastest sort completes, you can cancel the other
sorts. The .NET Framework 4 introduced the concept of cooperative cancellation,
which is a consistent model for cancelling tasks. As the name implies,
the model requires cooperation from running tasks. Tasks are responsible
for regularly checking for a cancellation request. After a cancellation
request has been received, a task should perform a timely and orderly
shutdown. Checking for cancellation might require polling, and that can
easily become a performance sink. For this reason, if it is required, be
careful when implementing a polling strategy.
Here are the steps required for the cooperative cancellation model:
Create an instance of the CancellationTokenSource class, which is a wrapper for a cancellation token.
Pass the actual cancellation token (the CancellationTokenSource.Token property) as a parameter to the cooperating tasks.
From the original thread, call the CancellationTokenSource.Cancel method to make a cancellation request.
In the task, check the CancellationToken.IsCancellationRequested property for a cancellation request.
After preparing for cancellation, for example by preserving state information, the task should call the CancellationToken.ThrowIfCancellationRequest method. This throws the OperationCanceledException exception and cancels the task.
You can inspect whether a task was canceled. Check Task.Status for TaskStatus.Canceled.
The use of CancellationTokenSource and CancellationToken
types is not restricted to tasks.
The next tutorial demonstrates the cooperative model for cancellation. In this example, you will create a CancellationToken that is passed into a task. In the task, you will periodically check for a cancellation request.
Create a task that throws a cancellation exception
In this procedure, the joining thread catches and handles the unhandled exception.
Create a console application. Before the Main function, add a DoSomething method. To emulate a compute-bound task, the task will simply spin and burn processor cycles. This is done with the Thread.SpinWait method.
static void DoSomething() { Thread.SpinWait(4000); }
In the Main function, create a CancellationTokenSource object. Afterward, initialize a cancellation token with the CancellationTokenSource.Token property.
CancellationTokenSource cancellationSource =
new CancellationTokenSource();
CancellationToken token = cancellationSource.Token;
Start a try/catch block for handling exceptions.
try {
You can now create and start a new task by using the TaskFactory.StartNew method. Initialize the task with a lambda expression.
Task TaskA=Task.Factory.StartNew(() => {
In the lambda expression, you need a while loop. In the loop, call the compute-bound method. Check whether an exception is requested by using the CancellationToken.IsCancellationRequested
property. If cancellation is requested, throw a cancellation exception.
Notice that you also pass the cancellation token to the task as the
last parameter of the StartNew method.
while (true) {
DoSomething();
if (token.IsCancellationRequested) {
token.ThrowIfCancellationRequested();
}
}}, token);
In the joining thread, cancel the task. Then wait for the task to complete and observe the cancellation exception.
cancellationSource.Cancel();
TaskA.Wait(); }
In the catch block, catch the cancellation exception and display the message.
Build and run the application.
Here is the complete application.
static void Main(string[] args)
{
CancellationTokenSource cancellationSource =
new CancellationTokenSource();
CancellationToken token = cancellationSource.Token;
try
{
Task TaskA=Task.Factory.StartNew(() =>
{
while (true)
{
DoSomething();
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}, token);
cancellationSource.Cancel();
TaskA.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.InnerException.Message);
}
}
}
1. A Cancellation Example
Here is the sample code
to implement cancellation in the bubble sort region of the sorting
application. Similar changes can be made in the regions for the
insertion and pivot sorts. The three sorting tasks are given the same
cancellation token. Because they share the same token, all of the
sorting algorithms can be cancelled as a group. For this reason, you
create the cancellation token only once before entering the sort
regions.
CancellationTokenSource cancellationSource =
new CancellationTokenSource();
CancellationToken token = cancellationSource.Token;
// Bubble Sort Region
List<int> bubbleList = integerList.ToList();
Task taskBubbleSort = new Task(() => {
sortBarrier.SignalAndWait();
using (new SortResults("Bubble Sort")){
SortAlgorithms.BubbleSort(bubbleList, token);
}
}, token);
taskBubbleSort.Start();
Because
each sort is different, cancellation polling occurs in different
locations in each sort algorithm. When a cancellation request is
detected, the code throws an OperationCanceledException exception and cancels the task.
Here is the sample code for actually canceling the sort algorithms. The joining thread calls WaitAny. Unlike WaitAll, WaitAny
will return when the fastest task finishes, at which time you can
cancel the other sort algorithms that are still running, by calling CancellationTokenSource.Cancel.
Task.WaitAny(new Task[] { taskBubbleSort, taskInsertionSort, taskPivotSort });
cancellationSource.Cancel();